﻿using System.Diagnostics.Contracts;

namespace O1.Data.Traversal
{
    [ContractVerification(true)]
    public class Projection : Filter
    {
        private readonly Filter filter;

        private readonly PropertyInterests projectionInterest;

        private readonly PropertyInterests filterInterest;

        public Projection(PropertyInterests interest)
        {
            Contract.Ensures(this.filterInterest == PropertyInterests.None || this.filter != null);
            
            this.projectionInterest = interest;
            this.filterInterest = PropertyInterests.None;
        }

        public Projection(Filter filter)
        {
            Contract.Requires(filter != null);
            Contract.Ensures(this.filterInterest == PropertyInterests.None || this.filter != null);
            
            this.filter = filter;
            this.filterInterest = filter.PropertyInterest;
            this.projectionInterest = filter.PropertyInterest;
        }

        public override PropertyInterests PropertyInterest
        {
            get
            {
                return this.projectionInterest;
            }
        }

        public override ObservationInterests StartVertex(CellAddress vertex)
        {
            var interest = this.filter != null ? this.filter.StartVertex(vertex) : ObservationInterests.Interested;
            if (interest == ObservationInterests.Interested)
            {
                this.Project(vertex);
            }

            return interest;
        }

        public override ObservationInterests StartEdge(Edge edge, EdgeDirections direction)
        {
            var interest = this.filter != null ? this.filter.StartEdge(edge, direction) : ObservationInterests.Interested;
            if (interest == ObservationInterests.Interested)
            {
                this.Project(edge, direction);
            }

            return interest;
        }

        public override ObservationInterests EndVertex(ObservationInterests vertexState)
        {
            var interest = this.filter != null ? this.filter.EndVertex(vertexState) : ObservationInterests.Interested;
            if (vertexState == ObservationInterests.Interested)
            {
                this.ProjectEndVertex(ObservationInterests.Interested);
            }
            
            return interest;
        }

        public override ObservationInterests EndEdge(ObservationInterests edgeState)
        {
            var interest = this.filter != null ? this.filter.EndEdge(edgeState) : ObservationInterests.Interested;
            if (edgeState == ObservationInterests.Interested)
            {
                this.ProjectEndEdge(ObservationInterests.Interested);
            }

            return interest;
        }

        public override ObservationInterests Evaluate(DateProperty value)
        {
            Contract.Assume(this.filterInterest.HasFlag(PropertyInterests.Date) && this.filter != null);

            var interest = this.filterInterest.HasFlag(PropertyInterests.Date) ? this.filter.Evaluate(value) : ObservationInterests.Interested;
            if (interest == ObservationInterests.Interested)
            {
                this.Project(value);
            }

            return interest;
        }

        public override ObservationInterests Evaluate(DateTimeProperty value)
        {
            Contract.Assume(this.filterInterest.HasFlag(PropertyInterests.DateTime) && this.filter != null);

            var interest = this.filterInterest.HasFlag(PropertyInterests.DateTime) ? this.filter.Evaluate(value) : ObservationInterests.Interested;
            if (interest == ObservationInterests.Interested)
            {
                this.Project(value);
            }

            return interest;
        }

        public override ObservationInterests Evaluate(DecimalProperty value)
        {
            Contract.Assume(this.filterInterest.HasFlag(PropertyInterests.Decimal) && this.filter != null);

            var interest = this.filterInterest.HasFlag(PropertyInterests.Decimal) ? this.filter.Evaluate(value) : ObservationInterests.Interested;
            if (interest == ObservationInterests.Interested)
            {
                this.Project(value);
            }

            return interest;
        }

        public override ObservationInterests Evaluate(HashProperty value)
        {
            Contract.Assume(this.filterInterest.HasFlag(PropertyInterests.Hash) && this.filter != null);

            var interest = this.filterInterest.HasFlag(PropertyInterests.Hash) ? this.filter.Evaluate(value) : ObservationInterests.Interested;
            if (interest == ObservationInterests.Interested)
            {
                this.Project(value);
            }

            return interest;
        }

        public override ObservationInterests Evaluate(Int16Property value)
        {
            Contract.Assume(this.filterInterest.HasFlag(PropertyInterests.Int16) && this.filter != null);

            var interest = this.filterInterest.HasFlag(PropertyInterests.Int16) ? this.filter.Evaluate(value) : ObservationInterests.Interested;
            if (interest == ObservationInterests.Interested)
            {
                this.Project(value);
            }

            return interest;
        }

        public override ObservationInterests Evaluate(Int32Property value)
        {
            Contract.Assume(this.filterInterest.HasFlag(PropertyInterests.Int32) && this.filter != null);

            var interest = this.filterInterest.HasFlag(PropertyInterests.Int32) ? this.filter.Evaluate(value) : ObservationInterests.Interested;
            if (interest == ObservationInterests.Interested)
            {
                this.Project(value);
            }

            return interest;
        }

        public override ObservationInterests Evaluate(Int64Property value)
        {
            Contract.Assume(this.filterInterest.HasFlag(PropertyInterests.Int64) && this.filter != null);

            var interest = this.filterInterest.HasFlag(PropertyInterests.Int64) ? this.filter.Evaluate(value) : ObservationInterests.Interested;
            if (interest == ObservationInterests.Interested)
            {
                this.Project(value);
            }

            return interest;
        }

        public override ObservationInterests Evaluate(StringProperty value)
        {
            Contract.Assume(this.filterInterest.HasFlag(PropertyInterests.String) && this.filter != null);

            var interest = this.filterInterest.HasFlag(PropertyInterests.String) ? this.filter.Evaluate(value) : ObservationInterests.Interested;
            if (interest == ObservationInterests.Interested)
            {
                this.Project(value);
            }

            return interest;
        }

        protected virtual void ProjectEndVertex(ObservationInterests interest)
        {
        }

        protected virtual void ProjectEndEdge(ObservationInterests interest)
        {
        }
        
        protected virtual void Project(CellAddress value)
        {
        }

        protected virtual void Project(Edge value, EdgeDirections direction)
        {
        }

        protected virtual void Project(DateProperty value)
        {
        }

        protected virtual void Project(DateTimeProperty value)
        {
        }

        protected virtual void Project(DecimalProperty value)
        {
        }

        protected virtual void Project(HashProperty value)
        {
        }

        protected virtual void Project(Int16Property value)
        {
        }

        protected virtual void Project(Int32Property value)
        {
        }

        protected virtual void Project(Int64Property value)
        {
        }

        protected virtual void Project(StringProperty value)
        {
        }
    }
}
